package com.ego.seckill.service.impl;

import com.ego.auth.entity.UserInfo;
import com.ego.exception.EgoException;
import com.ego.exception.ExceptionEnum;
import com.ego.pojo.Stock;
import com.ego.pojo.utils.IdWorker;
import com.ego.seckill.client.GoodsClient;
import com.ego.seckill.interceptor.LoginInterceptor;
import com.ego.seckill.mapper.SeckillGoodsMapper;
import com.ego.seckill.pojo.SeckillGoods;
import com.ego.seckill.service.SeckillService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author Ares
 * @date 2020-12-17
 */
@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    @Resource
    private SeckillGoodsMapper seckillGoodsMapper;

    @Resource
    private GoodsClient goodsClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private IdWorker idWorker;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private AmqpTemplate amqpTemplate;

    private static final String EGO_SECKILL_STOCK_KEY = "ego:seckill:stock";

    private static final String EGO_SECKILL_PATH_KEY = "ego:seckill:path";

    @Override
    @Transactional(readOnly = true)
    public List<SeckillGoods> findAll() {
        List<SeckillGoods> result = seckillGoodsMapper.selectList(null);
        //填充视图属性
        List<Long> skuIdList = result.stream().map(seckillGoods -> seckillGoods.getSkuId()).collect(Collectors.toList());
        List<Stock> stockList = goodsClient.queryStockListByIds(skuIdList).getBody();

        AtomicInteger index = new AtomicInteger();
        result.forEach(seckillGoods -> {
            int i = index.getAndIncrement();
            seckillGoods.setStock(stockList.get(i).getSeckillStock());
            seckillGoods.setSeckillTotal(stockList.get(i).getSeckillTotal());
        });
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public SeckillGoods findById(Long id) {
        return seckillGoodsMapper.selectById(id);
    }

    @Override
    public String createSeckillPath(Long seckillGoodsId, Long skuId) {
        String result = null;

        //判断是否有足够库存
        Integer stock = Integer.valueOf(stringRedisTemplate.opsForHash().get(EGO_SECKILL_STOCK_KEY, skuId.toString()).toString());
        if (stock <= 0) {
            EgoException.error(log, ExceptionEnum.STOCK_NOT_ENOUGH);
        }
        //查询redis是否有缓存
        UserInfo currUser = LoginInterceptor.getCurrUser();
        String pathKey = currUser.getId() + "-" + seckillGoodsId;
        if (stringRedisTemplate.opsForHash().hasKey(EGO_SECKILL_PATH_KEY, pathKey)) {
            result = stringRedisTemplate.opsForHash().get(EGO_SECKILL_PATH_KEY, pathKey).toString();

            if (StringUtils.isNotEmpty(result)) {
                return result;
            }
        }

        //创建唯一地址(uuid/idworker),存入redis
        result = String.valueOf(idWorker.nextId());
        stringRedisTemplate.opsForHash().put(EGO_SECKILL_PATH_KEY, pathKey, result);
        return result;
    }

    @Override
    public String seckill(String path, SeckillGoods seckillGoods) {
        String result = null;
        try {
            //1.判断地址是否有效
            UserInfo currUser = LoginInterceptor.getCurrUser();
            String pathKey = currUser.getId() + "-" + seckillGoods.getId();
            if( !stringRedisTemplate.opsForHash().hasKey(EGO_SECKILL_PATH_KEY, pathKey))
            {
                EgoException.error(log,ExceptionEnum.SECKILL_PATH_EXCEPTION);
            }
            String redisPath = stringRedisTemplate.opsForHash().get(EGO_SECKILL_PATH_KEY, pathKey).toString();
            if (!redisPath.equals(path)) {
                EgoException.error(log,ExceptionEnum.SECKILL_PATH_EXCEPTION);
            }

            //2.判断缓存标记是否有效

            //3.分布式锁
            RLock lock = null;
            try {
                String lockKey = "buying_" + seckillGoods.getSkuId();
                lock = redissonClient.getLock(lockKey);
                HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();
                String skuKey = seckillGoods.getSkuId().toString();
                if (lock.tryLock(30L, TimeUnit.SECONDS)) {
                    //4.判断redis库存够不够
                    if (hashOperations.hasKey(EGO_SECKILL_STOCK_KEY,skuKey )) {
                        Integer stock = Integer.valueOf(hashOperations.get(EGO_SECKILL_STOCK_KEY,skuKey).toString());
                        if (stock <= 0) {
                            EgoException.error(log, ExceptionEnum.STOCK_NOT_ENOUGH);
                        }
                        //5.减库存
                        hashOperations.increment(EGO_SECKILL_STOCK_KEY, skuKey, -1);
                        //6.异步发消息
                        Map<String, Object> map = new HashMap<>(4);

                        result = String.valueOf(idWorker.nextId());
                        map.put("orderId", result);
                        map.put("skuId", seckillGoods.getSkuId());
                        map.put("price", seckillGoods.getSeckillPrice());
                        map.put("userId", currUser.getId());
                        amqpTemplate.convertAndSend("ego.seckill.exchange","ego.seckill",map);
                    } else {
                        EgoException.error(log, ExceptionEnum.SKU_NOT_FOUND);
                    }
                }
            }finally {
                //解锁
                lock.unlock();
            }
        } catch (Exception e) {
            EgoException.error(log,ExceptionEnum.SECKILL_EXCEPTION);
        }
        return result;
    }
}
